use hypers::hyper::StatusCode;
use hypers::prelude::*;
use hypers::tracing::{debug, info};
use hypers::{async_trait, Hook};
use std::fs::create_dir_all;
use std::path::Path;

const MAX_SIZE: u64 = 1024 * 1024 * 10;
static INDEX_HTML: &str = r#"<!DOCTYPE html>
<html>
    <head>
        <title>Upload file</title>
    </head>
    <body>
        <h1>Upload file</h1>
        <form action="/unlimit" method="post" enctype="multipart/form-data">
            <h3>Unlimit</h3>
            <input type="file" name="file" />
            <input type="submit" value="upload" />
        </form>
        <form action="/limited" method="post" enctype="multipart/form-data">
            <h3>Limited 10MiB</h3>
            <input type="file" name="file" />
            <input type="submit" value="upload" />
        </form>
    </body>
</html>
"#;

async fn upload(mut req: Request) -> Result<Response> {
    let file = req.file("file").await?;
    let dest = format!("temp/{}", file.name.clone().unwrap_or("file".to_owned()));
    debug!(dest, "upload file");
    std::fs::copy(file.path.clone(), Path::new(&dest))?;
    let res = Response::default();
    return Ok(res.render(Text::Plain(format!("File uploaded to {dest}"))));
}

pub struct MaxSize(pub u64);
#[async_trait]
impl Hook for MaxSize {
    async fn handle<'a>(&'a self, req: Request, next: Next<'a>) -> Response {
        use hypers::hyper::body::Body;
        let size_hint = req.body().size_hint().upper();
        println!("url = {}", req.uri().path());
        println!("size_hint = {:?}", size_hint);
        let response = Response::default();
        if let Some(upper) = size_hint {
            if upper > self.0 {
                return response.render(StatusCode::PAYLOAD_TOO_LARGE).render(
                    "Payload Too Large The request is larger than the server is willing or able to process.",
                );
            }
            return next.next(req).await;
        }
        response
            .render(StatusCode::BAD_REQUEST)
            .render("Request body size is unknown.")
    }
}

#[tokio::main]
async fn main() -> Result<()> {
    tracing_subscriber::fmt().init();
    create_dir_all("temp")?;

    let mut root = Router::default();
    root.get("/", |_| async {
        Text::Html(INDEX_HTML) // http://127.0.0.1:7878/
    })
    .post("/limited", upload)
    .post("/unlimit", upload);
    root.hook(MaxSize(MAX_SIZE), vec!["/limited"], vec!["/unlimit"]);

    info!("root = {:#?}", root);
    let listener = hypers::TcpListener::bind("127.0.0.1:7878").await?;
    hypers::listen(root, listener).await
}
